package com.jingdianjichi.subject.infra.basic.service.impl;

import com.jingdianjichi.subject.common.entity.PageResult;
import com.jingdianjichi.subject.common.enums.SubjectInfoTypeEnum;
import com.jingdianjichi.subject.infra.basic.entity.EsSubjectFields;
import com.jingdianjichi.subject.infra.basic.entity.SubjectInfoEs;
import com.jingdianjichi.subject.infra.basic.es.EsIndexInfo;
import com.jingdianjichi.subject.infra.basic.es.EsRestClient;
import com.jingdianjichi.subject.infra.basic.es.EsSearchRequest;
import com.jingdianjichi.subject.infra.basic.es.EsSourceData;
import com.jingdianjichi.subject.infra.basic.service.SubjectEsService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.MapUtils;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.MatchQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;

@Service
@Slf4j
public class SubjectEsServiceImpl implements SubjectEsService {

    /**
     * 插入题目信息到Elasticsearch
     *
     * @param subjectInfoEs 要插入的题目信息
     * @return 是否插入成功
     */
    @Override
    public boolean insert(SubjectInfoEs subjectInfoEs) {
        // 创建ES数据源对象
        EsSourceData esSourceData = new EsSourceData();
        // 转换题目信息为ES可以接受的数据格式
        Map<String, Object> data = convert2EsSourceData(subjectInfoEs);
        // 设置文档ID和数据内容
        esSourceData.setDocId(subjectInfoEs.getDocId().toString());
        esSourceData.setData(data);
        // 插入文档到ES
        return EsRestClient.insertDoc(getEsIndexInfo(), esSourceData);
    }

    /**
     * 转换题目信息为ES数据源格式
     * 用于将题目信息对象（SubjectInfoEs）转换为Elasticsearch所需的数据格式。
     * @param subjectInfoEs 题目信息
     * @return 转换后的数据映射
     */
    private Map<String, Object> convert2EsSourceData(SubjectInfoEs subjectInfoEs) {
        // 创建一个空的HashMap，用于存放转换后的数据。
        Map<String, Object> data = new HashMap<>();
        data.put(EsSubjectFields.SUBJECT_ID, subjectInfoEs.getSubjectId()); // 设置题目id
        data.put(EsSubjectFields.DOC_ID, subjectInfoEs.getDocId());          // 设置文档id
        data.put(EsSubjectFields.SUBJECT_NAME, subjectInfoEs.getSubjectName()); // 设置题目名称
        data.put(EsSubjectFields.SUBJECT_ANSWER, subjectInfoEs.getSubjectAnswer()); // 设置题目答案
        data.put(EsSubjectFields.SUBJECT_TYPE, subjectInfoEs.getSubjectType()); // 设置题目类型
        data.put(EsSubjectFields.CREATE_USER, subjectInfoEs.getCreateUser()); // 设置题目创建者
        data.put(EsSubjectFields.CREATE_TIME, subjectInfoEs.getCreateTime()); // 设置题目创建时间
        // 返回填充好的数据映射。
        return data;
    }


    /**
     * 查询题目列表
     *
     * @param req 查询请求参数
     * @return 分页的题目信息列表
     */
    @Override
    public PageResult<SubjectInfoEs> querySubjectList(SubjectInfoEs req) {
        // 创建分页结果对象
        PageResult<SubjectInfoEs> pageResult = new PageResult<>();
        // 创建ES查询请求
        EsSearchRequest esSearchRequest = createSearchListQuery(req);
        // 执行查询
        SearchResponse searchResponse = EsRestClient.searchWithTermQuery(getEsIndexInfo(), esSearchRequest);
        // 准备题目信息列表
        List<SubjectInfoEs> subjectInfoEsList = new LinkedList<>();
        SearchHits searchHits = searchResponse.getHits();
        if (searchHits == null || searchHits.getHits() == null) {
            // 如果没有查询到数据，设置空的结果并返回
            pageResult.setPageNo(req.getPageNo());
            pageResult.setPageSize(req.getPageSize());
            pageResult.setRecords(subjectInfoEsList);
            pageResult.setTotal(0);
            return pageResult;
        }
        // 遍历查询结果，转换为题目信息对象并添加到列表中
        SearchHit[] hits = searchHits.getHits();
        for (SearchHit hit : hits) {
            // 将ES查询结果转换为题目信息对象的方法
            SubjectInfoEs subjectInfoEs = convertResult(hit);
            if (Objects.nonNull(subjectInfoEs)) {
                subjectInfoEsList.add(subjectInfoEs);
            }
        }
        // 设置分页结果的各个字段并返回
        pageResult.setPageNo(req.getPageNo());  // 设置页码
        pageResult.setPageSize(req.getPageSize());  // 设置单页展示的题目数量
        pageResult.setRecords(subjectInfoEsList);   // 将查到的所有题目内容封装到结果中
        pageResult.setTotal(Long.valueOf(searchHits.getTotalHits().value).intValue());  // 设置总页数
        return pageResult;
    }

    /**
     * 将ES查询结果转换为题目信息对象的方法。这个过程涉及到从Elasticsearch的查询结果中提取数据，并将这些数据映射到我们的题目信息实体类中。
     *
     * @param hit Elasticsearch的查询结果中的单个命中记录。
     * @return 转换后的题目信息对象，如果查询结果为空，则返回null。
     */
    private SubjectInfoEs convertResult(SearchHit hit) {
        // 获取ES查询结果中的_source字段，它包含了所有的字段值。
        Map<String, Object> sourceAsMap = hit.getSourceAsMap();
        // 如果查询结果为空，则直接返回null，表示没有查询到任何数据。
        if (CollectionUtils.isEmpty(sourceAsMap)) {
            return null;
        }

        // 创建一个新的题目信息对象，用于存放转换后的数据。
        SubjectInfoEs result = new SubjectInfoEs();

        // 从_source中提取题目ID，并设置到题目信息对象中。
        result.setSubjectId(MapUtils.getLong(sourceAsMap, EsSubjectFields.SUBJECT_ID));
        // 从_source中提取题目名称，并设置到题目信息对象中。
        result.setSubjectName(MapUtils.getString(sourceAsMap, EsSubjectFields.SUBJECT_NAME));
        // 从_source中提取题目答案，并设置到题目信息对象中。
        result.setSubjectAnswer(MapUtils.getString(sourceAsMap, EsSubjectFields.SUBJECT_ANSWER));
        // 从_source中提取文档ID，并设置到题目信息对象中。
        result.setDocId(MapUtils.getLong(sourceAsMap, EsSubjectFields.DOC_ID));
        // 从_source中提取题目类型，并设置到题目信息对象中。
        result.setSubjectType(MapUtils.getInteger(sourceAsMap, EsSubjectFields.SUBJECT_TYPE));
        // 将查询得分（hit.getScore()）转换为百分制，并四舍五入到小数点后两位，然后设置到题目信息对象中。
        result.setScore(new BigDecimal(String.valueOf(hit.getScore()))
                .multiply(new BigDecimal("100.00")).setScale(2, RoundingMode.HALF_UP));

        // 处理查询结果中的高亮字段。
        Map<String, HighlightField> highlightFields = hit.getHighlightFields();
        // 检查是否存在对题目名称的高亮处理结果
        HighlightField subjectNameField = highlightFields.get(EsSubjectFields.SUBJECT_NAME);
        // 如果存在高亮字段，则进行处理
        if(Objects.nonNull(subjectNameField)){
            // 获取所有高亮片段
            Text[] fragments = subjectNameField.getFragments();
            // 使用StringBuilder来构建最终的高亮文本
            StringBuilder subjectNameBuilder = new StringBuilder();
            // 遍历所有片段，将它们拼接起来
            for (Text fragment : fragments) {
                subjectNameBuilder.append(fragment);
            }
            // 将拼接后的高亮文本设置为题目名称
            result.setSubjectName(subjectNameBuilder.toString());
        }
        // 检查是否存在对题目答案的高亮处理结果
        HighlightField subjectAnswerField = highlightFields.get(EsSubjectFields.SUBJECT_ANSWER);
        // 如果存在高亮字段，则进行处理
        if(Objects.nonNull(subjectAnswerField)){
            // 获取所有高亮片段
            Text[] fragments = subjectAnswerField.getFragments();
            // 使用StringBuilder来构建最终的高亮文本
            StringBuilder subjectAnswerBuilder = new StringBuilder();
            // 遍历所有片段，将它们拼接起来
            for (Text fragment : fragments) {
                subjectAnswerBuilder.append(fragment);
            }
            // 将拼接后的高亮文本设置为题目答案
            result.setSubjectAnswer(subjectAnswerBuilder.toString());
        }
        // 返回填充完成的题目信息对象。
        return result;
    }


    /**
     * 创建题目列表查询的ES查询请求（从ES中进行全文检索）
     *
     * @param req 查询请求参数
     * @return ES查询请求
     */
    private EsSearchRequest createSearchListQuery(SubjectInfoEs req) {
        EsSearchRequest esSearchRequest = new EsSearchRequest();
        BoolQueryBuilder bq = new BoolQueryBuilder();
        // 创建针对题目名称的匹配查询
        MatchQueryBuilder subjectNameQueryBuilder =
                QueryBuilders.matchQuery(EsSubjectFields.SUBJECT_NAME, req.getKeyWord());
        bq.should(subjectNameQueryBuilder);
        subjectNameQueryBuilder.boost(2); // 设置权重提高题目名称匹配的重要性

        // 创建针对题目答案的匹配查询
        MatchQueryBuilder subjectAnswerQueryBuilder =
                QueryBuilders.matchQuery(EsSubjectFields.SUBJECT_ANSWER, req.getKeyWord());
        bq.should(subjectAnswerQueryBuilder);

        // 创建针对题目类型的匹配查询，只查询指定类型的题目
        MatchQueryBuilder subjectTypeQueryBuilder =
                QueryBuilders.matchQuery(EsSubjectFields.SUBJECT_TYPE, SubjectInfoTypeEnum.BRIEF.getCode());
        bq.must(subjectTypeQueryBuilder);
        bq.minimumShouldMatch(1); // 至少匹配一个条件

        // 设置高亮显示
        HighlightBuilder highlightBuilder = new HighlightBuilder().field("*").requireFieldMatch(false);
        highlightBuilder.preTags("<span style = \"color:red\">"); // 高亮开始标签
        highlightBuilder.postTags("</span>"); // 高亮结束标签

        // 设置查询请求的各个参数
        esSearchRequest.setBq(bq);
        esSearchRequest.setHighlightBuilder(highlightBuilder);
        esSearchRequest.setFields(EsSubjectFields.FIELD_QUERY);
        esSearchRequest.setFrom((req.getPageNo() - 1) * req.getPageSize()); // 计算分页偏移量
        esSearchRequest.setSize(req.getPageSize()); // 设置每页显示的数量
        esSearchRequest.setNeedScroll(false); // 不需要滚动查询
        return esSearchRequest;
    }

    /**
     * 获取ES索引信息
     *
     * @return ES索引信息对象
     */
    private EsIndexInfo getEsIndexInfo() {
        EsIndexInfo esIndexInfo = new EsIndexInfo();
        esIndexInfo.setClusterName("6ed5e607002a"); // ES集群名
        esIndexInfo.setIndexName("subject_index"); // 索引名
        return esIndexInfo;
    }
}
